/*
 * -----------------------------------------------------------------------------
 *
 *   Copyright (C) 2005, 2006 by Dmitry Morozhnikov <dmiceman AT mail.ru>
 *   Original version (http://ubiz.ru/dm/)
 *
 *   Copyright (C) 2007 by Ryan Thomas
 *   Protection against cornercase when read request is exactly equal to
 *   the uncompressed file size
 *
 *   Copyright (C) 2007 by Lionel Tricon <lionel.tricon AT free.fr>
 *   Build a class to extract files from the iso image
 *
 * -----------------------------------------------------------------------------
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the
 *   Free Software Foundation, Inc.,
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * -----------------------------------------------------------------------------
 *  CmgUtl::CmgUtl
 *  CmgUtl::~CmgUtl
 *  CmgUtl::GetLinkInfo
 *  CmgUtl::GetFileInfo
 *  CmgUtl::ExtractFile
 *  CmgUtl::ReadFile
 *  CmgUtl::ReadDir
 *  CmgUtl::IsDir
 *  CmgUtl::IsLink
 *  CmgUtl::findLookupTable
 *  CmgUtl::insertLookupTable
 *  CmgUtl::searchFileIsoImage
 *  CmgUtl::readDirIsoImage
 *  CmgUtl::isofs_real_preinit
 *  CmgUtl::isofs_real_init
 *  CmgUtl::isofs_check_rr
 *  CmgUtl::isofs_read_raw_block
 *  CmgUtl::isofs_date
 *  CmgUtl::isofs_direntry2stat
 *  CmgUtl::isofs_fix_entry
 *  CmgUtl::isofs_free_inode
 *  CmgUtl::isofs_parse_zisofs_header
 *  CmgUtl::isofs_parse_sa
 *  CmgUtl::isofs_real_read_zf
 *  CmgUtl::dstr
 * -----------------------------------------------------------------------------
 */

#include "cmg_utl.hh"

CmgUtl::CmgUtl(
string p_path,
bool p_debug,
string p_iocharset
)
{
    // mutex initialization
    pthread_mutex_init(&this->_fd_mutex, NULL);
    pthread_mutex_init(&this->_lookup_table_mutex, NULL);

    // set up all variables
    this->_iocharset = p_iocharset;
    this->_debug = p_debug;

    // open the image file
    this->_filedesc = open(p_path.c_str(), O_RDONLY);
    if (this->_filedesc == -1)
    {
        perror("Can`t open image file");
        return;
    }

    // will exit in case of failure
    int rc = this->isofs_real_preinit((char*)p_path.c_str(), this->_filedesc);
    if (rc < -1)
    {
        close(this->_filedesc);
        this->_filedesc = -1;
        return;
    }

    // isofs initialization
    (void)this->isofs_real_init();
}

CmgUtl::~CmgUtl(
void
)
{
    // close the image file
    close(this->_filedesc);

    // some clean up
    pthread_mutex_destroy(&this->_fd_mutex);
    pthread_mutex_destroy(&this->_lookup_table_mutex);
}

string
CmgUtl::GetLinkInfo(
string p_path,
bool p_search
)
{
    char *v_tempo;
    string v_foo;

    // we search the file into the iso image
    if (p_search) this->searchFileIsoImage((char*)p_path.c_str());
    isofs_inode *inode = (isofs_inode*)this->findLookupTable(p_path.c_str());
    if (inode != NULL)
    {
        v_tempo = (char*)malloc(inode->sl_len+1);
        if (v_tempo == NULL) return p_path;
        memset(v_tempo, '\0', inode->sl_len+1);
        memcpy(v_tempo, inode->sl, inode->sl_len);
        v_foo = v_tempo;
        free(v_tempo);
        return v_foo;
    }

    return p_path;
}

bool
CmgUtl::GetFileInfo(
string &p_path,
mode_t &p_mode,
long &p_size
)
{
    struct stat v_st;
    string oldpath;
    string newpath;

    // we search the file into the iso image in a
    // recursive way to deal with symbolic links
    for (oldpath=p_path;;)
    {
        this->searchFileIsoImage((char*)oldpath.c_str(), newpath);
        if (oldpath == newpath) break;
        oldpath = newpath;
    }

    isofs_inode *inode = (isofs_inode*)this->findLookupTable(newpath.c_str());
    if (inode == NULL)
    {
        if (this->_debug) fprintf(stderr, "read: know nothing about [%s]\n", newpath.c_str());
        return false;
    }

    // we check if the file is a directory
    struct iso_directory_record *record = inode->record;
    if (ISO_FLAGS_DIR(record->flags))
    {
        fprintf(stderr, "read: %s not a file\n", newpath.c_str());
        return false;
    }

    // we extract the data from the iso image
    if (isofs_direntry2stat(&v_st, inode) !=0) return false;

    p_mode = v_st.st_mode;
    p_size = v_st.st_size;
    p_path = newpath;
    return true;
}

int
CmgUtl::ExtractFile(
string p_path,
FILE* p_outfile
)
{
    char v_buffer[ISOFS_BLOCK_SIZE+1];
    bool isLink = this->IsLink(p_path);
    int retval, size=0;

    if (p_path.empty() || p_outfile==NULL) return 0;

    for (int i=0;;i++)
    {
        memset(v_buffer,'\0',ISOFS_BLOCK_SIZE+1);
        if (isLink) retval=this->ReadFile(this->GetRealPath(p_path,this->GetLinkInfo(p_path,true)), v_buffer, ISOFS_BLOCK_SIZE, i*ISOFS_BLOCK_SIZE);
        else retval=this->ReadFile(p_path, v_buffer, ISOFS_BLOCK_SIZE, i*ISOFS_BLOCK_SIZE);
        if (retval < 0) return retval;
        size += retval;
        fwrite(v_buffer, retval, 1, p_outfile);
        if (retval != ISOFS_BLOCK_SIZE) break;
    }

    return size;
}

int
CmgUtl::ReadFile(
string p_path,
char *p_out_buf,
size_t p_size,
size_t p_offset
)
{
    isofs_inode *inode = (isofs_inode*)this->findLookupTable(p_path.c_str());
    if (inode == NULL)
    {
        if (this->_debug) fprintf(stderr, "read: know nothing about [%s]\n", p_path.c_str());
        return -ENOENT;
    }

    struct iso_directory_record *record = inode->record;
    if (ISO_FLAGS_DIR(record->flags))
    {
        fprintf(stderr, "read: %s not a file\n", p_path.c_str());
        return -EINVAL;
    }
    if (ISO_FLAGS_HIDDEN(record->flags))
    {
        fprintf(stderr, "read: %s is a hidden file\n", p_path.c_str());
        // return -EPERM;
    }

    if (inode->ZF)                                                    // this file is compressed, handle it specially
    {
        return this->isofs_real_read_zf(inode, p_out_buf, p_size, p_offset);
    }

    size_t fsize = isonum_733(record->size);
    if (p_offset + p_size > fsize) p_size = fsize - p_offset;
    if (p_size < 1) return 0;

    int start_block = isonum_733(record->extent);
    if (start_block == 0) return 0;                                   // empty file

    int start = p_offset / _context.data_size;
    int end = (p_offset + p_size) / _context.data_size;
    int shift = p_offset % _context.data_size;
    int block = start_block + start;
    //     printf("read: path %s, size %d, offset %d, fsize %d, start %d, end %d, shift %d\n",
    //         p_path.c_str(), p_size, (int) p_offset, fsize, start, end, shift);

    char *buf = (char *) malloc(_context.data_size);
    if (!buf)
    {
        perror("Can`t malloc: ");
        return -ENOMEM;
    }

    int i;
    size_t total_size = 0;
    size_t size_left = p_size;
    int count = 0;

    for (i=start; i<=end; i++)
    {
        int len = this->isofs_read_raw_block(block, buf);
        if (len < 0)
        {
            free(buf);
            return len;
        }
        //         printf("read: block %d, len %d, size_left %d\n", block, len, size_left);

        if (len > (int)size_left)
        {
            len = size_left;
        }

        total_size += len;
        memcpy(p_out_buf + count * _context.data_size, buf + shift, len - shift);

        count++;
        shift = 0;
        size_left -= len;
        block++;
    }

    free(buf);
    return total_size;
}

bool
CmgUtl::ReadDir(
string p_path,
vector<string> &p_list
)
{
    char path_src[PATH_MAX];
    strcpy(path_src, (char*)p_path.c_str());

    isofs_inode *inode = (isofs_inode*)this->findLookupTable(p_path.c_str());
    if (inode == NULL)
    {
      this->searchFileIsoImage(path_src);
      inode = (isofs_inode*)this->findLookupTable(p_path.c_str());
    }
    if (this->readDirIsoImage(path_src,p_list) == 0) return true;
    return false;
}

bool
CmgUtl::IsDir(
string p_path
)
{
    isofs_inode *inode = (isofs_inode*)this->findLookupTable(p_path.c_str());
    if (inode == NULL)
    {
      this->searchFileIsoImage((char*)p_path.c_str());
      inode = (isofs_inode*)this->findLookupTable(p_path.c_str());
    }

    if (inode!=NULL && ISO_FLAGS_DIR(inode->record->flags)) return true;
    return false;
}

bool
CmgUtl::IsLink(
string p_path
)
{
    isofs_inode *inode = (isofs_inode*)this->findLookupTable(p_path.c_str());
    if (inode == NULL)
    {
        if (this->_debug) fprintf(stderr, "read: know nothing about [%s]\n", p_path.c_str());
        return false;
    }

    if (!ISO_FLAGS_DIR(inode->record->flags) && inode->sl_len!=0) return true;
    return false;
}

isofs_inode*
CmgUtl::findLookupTable(
string p_path
)
{
    map<string, isofs_inode*>::iterator v_iter;
    isofs_inode *v_inode;

    // lock the lookup table
    pthread_mutex_lock(&this->_lookup_table_mutex);

    // try to find reference of the path into the lookup table
    v_iter = this->_lookup_table.find(p_path);
    if (v_iter == this->_lookup_table.end())
    {
        pthread_mutex_unlock(&this->_lookup_table_mutex);
        return NULL;
    }

    // keep a pointer on the reference
    v_inode = v_iter->second;

    // unlock the lookup table
    pthread_mutex_unlock(&this->_lookup_table_mutex);

    return v_inode;
}

void
CmgUtl::insertLookupTable(
string p_path,
isofs_inode *p_inode
)
{
    // lock the lookup table
    pthread_mutex_lock(&this->_lookup_table_mutex);

    // test if the path is already here
    if (this->_lookup_table.find(p_path) != this->_lookup_table.end())
    {
        pthread_mutex_unlock(&this->_lookup_table_mutex);
        return;
    }

    // insert a new value into the map
    this->_lookup_table[p_path] = p_inode;

    // unlock the lookup table
    pthread_mutex_unlock(&this->_lookup_table_mutex);
}

void
CmgUtl::searchFileIsoImage(
char *searchfile
)
{
    string newpath;
    this->searchFileIsoImage(searchfile, newpath);
}

void
CmgUtl::searchFileIsoImage(
char *searchfile,
string &newpath
)
{
    char path_src[PATH_MAX];
    char path_dest[PATH_MAX];
    char *saveptr;
    char *pch;

    strcpy(path_dest, "");
    strcpy(path_src, searchfile);
    (void)this->readDirIsoImage((char*)"/");
    pch = strtok_r(path_src, "/", &saveptr);

    while (pch != NULL)
    {
        strcat(path_dest, "/");
        strcat(path_dest, pch);
        if (this->readDirIsoImage((char*)path_dest) != 0) break;
        pch = strtok_r(NULL, "/", &saveptr);
    }

    newpath = path_dest;
}

// in case we deal with a symbolic link, return new path into p_path
// -EINVAL, attempt to read empty path name
// -ENOTDIR, is not a directory
// -ENOMEM, malloc error
// -EIO, file format error
int
CmgUtl::readDirIsoImage(
char *p_path,
bool p_retlist,
vector<string> &p_list
)
{
    int rc;
    char *buf = NULL;
    if (p_path[0] == '\0') return -EINVAL;

    isofs_inode *current_inode = (isofs_inode*)this->findLookupTable(p_path);
    if (current_inode == NULL) return -EIO;

    if (!ISO_FLAGS_DIR(current_inode->record->flags) && current_inode->sl_len!=0)
    {
        string newpath = this->GetRealPath(p_path ,this->GetLinkInfo(p_path));
        this->searchFileIsoImage((char*)newpath.c_str());
        strcpy(p_path, (char*)newpath.c_str());
        return this->readDirIsoImage(p_path, p_retlist, p_list);
    }

    struct iso_directory_record *current = current_inode->record;
    if (!ISO_FLAGS_DIR(current->flags)) return -ENOTDIR;

    size_t current_size = isonum_733(current->size);

    int block = isonum_733(current->extent);
    buf = (char *) malloc(_context.data_size);
    if (!buf) return -ENOMEM;

    size_t total_size = 0;
    int count = 1;
    int block_count = 0;
    off_t boff = 0;

    while (total_size <= current_size - sizeof(struct iso_directory_record))
    {
        rc = this->isofs_read_raw_block(block, buf);
        if (rc < 0) return rc;
        if (rc != (int)_context.data_size)
        {
            free(buf);
            return -EIO;
        }
        block_count++;

        if (boff > 0)
        {
            total_size += (_context.data_size - boff);
            boff = 0;
        }

        while (boff + sizeof(struct iso_directory_record) <= _context.data_size &&
            total_size <= current_size - sizeof(struct iso_directory_record))
        {

            struct iso_directory_record *record = (struct iso_directory_record *) (buf + boff);
            size_t record_length = isonum_711((unsigned char *) record->length);
            size_t name_len = isonum_711(record->name_len);
            size_t pad_len = ((name_len & 1) ? 0 : 1);            // padding byte if name_len is even
            size_t sa_len = record_length - name_len - sizeof(struct iso_directory_record) - pad_len;

            // printf("block %d, boff %d, total_size %d, current_size %d, record length %d, name_len %d, pad_len %d, sa_len %d\n",
            //     block, (int) boff, total_size, current_size, record_length, name_len, pad_len, sa_len);
            if (record_length == 0)
            {
                // possible end of directory or end of block
                total_size += (_context.data_size - boff);
                boff = 0;
                break;
            }
            if (record_length < sizeof(struct iso_directory_record))
            {
                if (count > 2)                                     // check if . and .. is already read
                {
                    // possible end of directory
                    // at least mkisofs does not set iso_directory_record.size correct
                    // (this is much possible it was my fault and misunderstanding -- dmiceman)
                    // but we need to try next block to be sure
                    /// TODO this is not clear: what to do if next block not contain next directory?
                    total_size += (_context.data_size - boff);
                    boff = 0;
                    break;
                }
                else
                {
                    free(buf);
                    return -EIO;
                }
            }
            if (name_len > NAME_MAX - 1)
            {
                free(buf);
                return -EIO;
            }
            /*if (sa_len < 0)
            {
                free(buf);
                return -EIO;
            }*/
            if (count > 2 && name_len == 1 && record->name[0] == 0)
            {
                // looks like this is normal situation to meet another directory because
                // there is no clear way to find directory end
                //                 fprintf(stderr, "readdir: next directory found while processing another directory! boff %d, total_size %d, current_size %d, block %d, count %d\n",
                //                     (int) boff, total_size, current_size, block, count);
                goto out;
            }

            isofs_inode *inode = (isofs_inode*)malloc(sizeof(isofs_inode));
            if (inode == NULL) return -ENOMEM;
            memset(inode, 0, sizeof(isofs_inode));
            inode->st_ino = _context.last_ino;
            _context.last_ino++;

            struct iso_directory_record *n_rec = (struct iso_directory_record*)malloc(sizeof(struct iso_directory_record));
            if (!n_rec) return -ENOMEM;
            memcpy(n_rec, record, sizeof(struct iso_directory_record));
            inode->record = n_rec;

            if (_context.susp || p_path[1] == '\0')                   // if susp is known to be present or this is a root dir ("/")
            {
                // printf("sa offset %d, sa_len %d\n",
                // sizeof(struct iso_directory_record) + name_len + pad_len, sa_len);
                rc = this->isofs_parse_sa(inode,
                    ((char *) record) + sizeof(struct iso_directory_record) + name_len + pad_len + _context.susp_skip,
                    sa_len);
                if (rc)
                {
                    free(buf);
                    this->isofs_free_inode(inode);
                    return rc;
                }
            }

            char *entry = (char *) malloc(NAME_MAX);
            if (!entry) return -ENOMEM;
            if (count == 1) strcpy(entry, ".");                   // . entry ('\0' on disk)
            else if (count == 2) strcpy(entry, "..");             // regular entry
            else
            {
                if (inode->NM)                                     // rrip NM entry present and in effect
                {
                    // printf("readdir: NM entry is in effect\n");
                    strncpy(entry, inode->nm, inode->nm_len);
                    entry[inode->nm_len] = '\0';
                }                                                 // regular ISO9660 filename
                else
                {
                    // printf("readdir: no NM entry found, name len %d\n", name_len);
                    // because there can be '\0' characters because using of UCS-2 encoding we need to use memcpy
                    memcpy(entry, (char *) record->name, name_len);
                    entry[name_len] = '\0';

                    // fixup entry -- lowercase, strip leading ';', etc..
                    entry = this->isofs_fix_entry(entry, name_len);
                    if (!entry)
                    {
                        this->isofs_free_inode(inode);
                        free(buf);
                        return -EIO;
                    }
                }
            }

            char absolute_entry[PATH_MAX];
            strcpy(absolute_entry, p_path);
            if (p_path[1] != '\0') strcat(absolute_entry, "/");         // not root dir
            strcat(absolute_entry, entry);
            if (this->findLookupTable(absolute_entry))
            {
                if (p_retlist) p_list.push_back(absolute_entry);
                // already in lookup cache
                this->isofs_free_inode(inode);
            }
            else
            {
                if (p_retlist) p_list.push_back(absolute_entry);
                else this->insertLookupTable(absolute_entry, inode);
                // printf("Add [%s]\n", absolute_entry);
            }

            free(entry);

            boff += record_length;
            total_size += record_length;
            count++;
        }

        // read next block

        block++;
    }

out:
    if (buf != NULL) free(buf);
    return 0;
}

int
CmgUtl::readDirIsoImage(
char *p_path,
vector<string> &p_list
)
{
    return this->readDirIsoImage(p_path, true, p_list);
}

int
CmgUtl::readDirIsoImage(
char *p_path
)
{
    vector<string> v_list;
    return this->readDirIsoImage(p_path, false, v_list);
}

int
CmgUtl::isofs_real_preinit(
char* imagefile,
int fd
)
{
    memset(&_context, 0, sizeof(isofs_context));

    _context.imagefile = imagefile;
    _context.fd = fd;

    // trying to read all volume descriptors
    struct iso_volume_descriptor *vd =
        (struct iso_volume_descriptor *) malloc(sizeof(struct iso_volume_descriptor));
    if (!vd)
    {
        perror("Can`t malloc: ");
        return -1;
    }
    int vd_num = 0;

    // defaults for iso
    _context.block_size = 2048;
    _context.data_size = 2048;
    _context.block_offset = 0;
    _context.file_offset = 0;

    enum
    {
        IDOFF_ISO_2048 = 2048 * 16,
        IDOFF_MODE1_2352 = 2352 * 16 + 16,
        IDOFF_MODE2_2352_RAW = 2352 * 16,
        IDOFF_MODE2_2352 = 2352 * 16 + 24,
        IDOFF_MODE2_2336 = 2336 * 16 + 16,
        IDOFF_NRG = 2048 * 16 + 307200,
    };
    int iso_offsets[] = {IDOFF_ISO_2048, IDOFF_MODE2_2336, IDOFF_MODE2_2352_RAW, IDOFF_NRG};
    // try to find CD001 identifier
    int i;
    for (i = 0; i < 4; i++)
    {
        if (lseek(fd, iso_offsets[i], SEEK_SET) == -1)
        {
            perror("can`t lseek() to next possible data start position; is it really supported file?");
            return -1;
        }
        ssize_t size = read(fd, vd, sizeof(struct iso_volume_descriptor));
        if (size != sizeof(struct iso_volume_descriptor))
        {
            fprintf(stderr, "only %d bytes read from position %d, %d required; is it really supported file?\n",
                size, iso_offsets[i], sizeof(struct iso_volume_descriptor));
            return -1;
        }
        char *vd_id = (char *) vd->id;
        if (strncmp("CD001", vd_id, 5) == 0)
        {
            // found CD001!
            // fill context with information about block size and block offsets
            _context.id_offset = iso_offsets[i];
            switch (iso_offsets[i])
            {
                case IDOFF_ISO_2048:
                    // normal iso file
                    // use defaults
                    break;
                case IDOFF_MODE2_2352_RAW:
                    _context.block_size = 2352;
                    break;
                case IDOFF_MODE2_2336:
                    _context.block_size = 2336;
                    _context.block_offset = 16;
                    break;
                case IDOFF_NRG:
                    _context.file_offset = 307200;
                    break;
                default:
                    break;
            }
            break;
        }
        else if (strncmp("CD001", vd_id+16, 5) == 0)
        {
            _context.id_offset = iso_offsets[i] + 16;
            _context.block_size = 2352;
            _context.block_offset = 16;
            break;
        }
        else if (strncmp("CD001", vd_id+24, 5) == 0)
        {
            _context.id_offset = iso_offsets[i] + 24;
            _context.block_size = 2352;
            _context.block_offset = 24;
            break;
        }
    }

    /*    printf("CD001 found at %d, bs %d, boff %d, ds %d\n",
                    _context.id_offset, _context.block_size, _context.block_offset, _context.data_size);*/
    while (1)
    {
        if (lseek(fd, _context.block_size * (16 + vd_num) +
            _context.block_offset + _context.file_offset, SEEK_SET) == -1)
        {
            perror("can`t lseek() to next volume descriptor");
            return -1;
        }
        ssize_t size = read(fd, vd, sizeof(struct iso_volume_descriptor));
        if (size != sizeof(struct iso_volume_descriptor))
        {
            fprintf(stderr, "only %d bytes read from volume descriptor %d, %d required\n",
                size, vd_num, sizeof(struct iso_volume_descriptor));
            return -1;
        }

        int vd_type = isonum_711((unsigned char *)vd->type);
        //         printf("init: found volume descriptor type %d, vd_num %d\n", vd_type, vd_num);

        if (strncmp("CD001", vd->id, 5) != 0)
        {
            if (vd_num > 16)
            {
                // no more trying
                fprintf(stderr, "init: wrong standard identifier in volume descriptor %d, exiting..\n", vd_num);
                return -1;
            }
            else
            {
                // try to continue
                fprintf(stderr, "init: wrong standard identifier in volume descriptor %d, skipping..\n", vd_num);
            }
        }
        else
        {
            switch (vd_type)
            {
                case ISO_VD_PRIMARY:
                    // check if this is only primary descriptor found
                    if (_context.pd.type[0])
                    {
                        fprintf(stderr, "init: primary volume descriptor already found, skipping..\n");
                    }
                    else
                    {
                        memcpy(&_context.pd, vd, sizeof(struct iso_volume_descriptor));
                        _context.root = (struct iso_directory_record *)& _context.pd.root_directory_record;
                        _context.data_size = isonum_723(_context.pd.logical_block_size);

                        if (!_context.block_size)
                        {
                            fprintf(stderr, "init: wrong block data size %d, using default 2048\n", _context.data_size);
                            _context.data_size = 2048;
                        }

                        if (_context.block_size != 2048)
                        {
                            // report unusual data block size
                            // later
                            // printf("Data block size: %d\n", _context.block_size);
                        }

                        if (this->isofs_check_rr(_context.root))
                        {
                            _context.pd_have_rr = 1;
                        }
                    }
                    break;

                case ISO_VD_SUPPLEMENTARY:
                {
                    struct iso_supplementary_descriptor *sd = (struct iso_supplementary_descriptor *) vd;

                    if (!_context.pd.type[0])
                    {
                        fprintf(stderr, "init: supplementary volume descriptor found, but no primary descriptor!\n");
                        return -1;
                    }
                    else
                    {
                        int joliet_level = 0;

                        if (sd->escape[0] == 0x25 && sd->escape[1] == 0x2f)
                        {
                            switch (sd->escape[2])
                            {
                                case 0x40:
                                    joliet_level = 1;
                                    break;
                                case 0x43:
                                    joliet_level = 2;
                                    break;
                                case 0x45:
                                    joliet_level = 3;
                                    break;
                            }
                        }

                        int have_rr =
                            this->isofs_check_rr((struct iso_directory_record *) sd->root_directory_record);

                        // switch to SVD only if it contain RRIP or if PVD have no RRIP
                        // in other words, prefer VD with RRIP
                        if ((joliet_level && have_rr) ||
                            (have_rr && !_context.pd_have_rr) ||
                            (joliet_level && !_context.pd_have_rr))
                        {

                            _context.joliet_level = joliet_level;
                            memcpy(&_context.sd, vd, sizeof(struct iso_volume_descriptor));
                            _context.supplementary = 1;

                            _context.root = (struct iso_directory_record *) _context.sd.root_directory_record;

                            // printf("init: switching to supplementary descriptor %d, joliet_level %d, have_rr %d\n",
                            //     vd_num, _context.joliet_level, have_rr);
                        }
                        else
                        {
                            _context.joliet_level = 0;
                            // printf("init: found supplementary descriptor %d, flags %d\n",
                            //     vd_num, isonum_711(sd->flags));
                        }
                    }
                }
                break;

                case 0:
                    // boot record, not intresting..
                    break;

                case ISO_VD_END:
                    free(vd);
                    goto out;
                    break;

                default:
                    fprintf(stderr, "init: unsupported volume descriptor type %d, vd_num %d\n",
                        vd_type, vd_num);
                    break;
            }
        }

        vd_num += 1;
    }
    out:

    if (!_context.pd.type[0])
    {
        fprintf(stderr, "init: primary volume descriptor not found! exiting..\n");
        return -1;
    }

    _context.susp = 0;
    _context.susp_skip = 0;

    isofs_inode *inode = (isofs_inode *)malloc(sizeof(isofs_inode));
    if (inode == NULL)
    {
        perror("Can`t malloc: ");
        return -1;
    }
    memset(inode, 0, sizeof(isofs_inode));
    inode->record = _context.root;
    _context.last_ino++;                                               // set to 1
    inode->st_ino = _context.last_ino;
    _context.last_ino++;

    this->insertLookupTable((char*)"/", inode);
    return 0;
}

void*
CmgUtl::isofs_real_init(
void
)
{
    char buf[129];

    if (this->_debug)
    {
        if (_context.file_offset == 307200) printf("NRG image found\n");
        else if (_context.block_size == 2048) printf("ISO9660 image found\n");
        else if (_context.block_size == 2352 && _context.block_offset == 0) printf("MODE2 RAW BIN image found\n");
        else if (_context.block_size == 2352 && _context.block_offset == 16) printf("MODE1 BIN image found (or CCD MODE1 image, or MDF image)\n");
        else if (_context.block_size == 2352 && _context.block_offset == 24) printf("MODE2 BIN image found (or CCD MODE2 image)\n");
        else if (_context.block_size == 2336 && _context.block_offset == 16) printf("MODE2/2336 BIN image found\n");
        else printf("UNKNOWN image found; probably will not work\n");

        if (_context.block_size != 2048)
        {
            // report unusual data block size
            printf("Data block size: %d\n", _context.block_size);
        }

        printf("System Identifier                 : %s\n", this->dstr(buf, _context.pd.system_id, 32));
        printf("Volume Identifier                 : %.32s\n", this->dstr(buf, _context.pd.volume_id, 32));
        printf("Volume Set Identifier             : %.128s\n", this->dstr(buf, _context.pd.volume_set_id, 128));
        printf("Publisher Identifier              : %.128s\n", this->dstr(buf, _context.pd.publisher_id, 128));
        printf("Data Preparer Identifier          : %.128s\n", this->dstr(buf, _context.pd.preparer_id, 128));
        printf("Application Identifier            : %.128s\n", this->dstr(buf, _context.pd.application_id, 128));
        printf("Copyright File Identifier         : %.37s\n", this->dstr(buf, _context.pd.copyright_file_id, 37));
        printf("Abstract File Identifier          : %.37s\n", this->dstr(buf, _context.pd.abstract_file_id, 37));
        printf("Bibliographic File Identifier     : %.37s\n", this->dstr(buf, _context.pd.bibliographic_file_id, 37));
        printf("Volume Creation Date and Time     : %.17s\n", this->dstr(buf, _context.pd.creation_date, 17));
        printf("Volume Modification Date and Time : %.17s\n", this->dstr(buf, _context.pd.modification_date, 17));
        printf("Volume Expiration Date and Time   : %.17s\n", this->dstr(buf, _context.pd.expiration_date, 17));
        printf("Volume Effective Date and Time    : %.17s\n", this->dstr(buf, _context.pd.effective_date, 17));
    }

    return (void*) &_context;
}

int
CmgUtl::isofs_check_rr(
struct iso_directory_record *root_record
)
{
    int extent = isonum_733(root_record->extent);
    char *buf = (char *) malloc(_context.data_size);                   // can we use "standard" 2048 there?
    if (!buf)
    {
        perror("Can`t malloc: ");
        return -ENOMEM;
    }

    int rc = this->isofs_read_raw_block(extent, buf);
    if (rc < 0)
    {
        free(buf);
        return 0;
    }

    struct iso_directory_record *record = (struct iso_directory_record *) buf;
    size_t record_length = isonum_711((unsigned char*) record->length);
    size_t name_len = isonum_711(record->name_len);
    size_t pad_len = ((name_len & 1) ? 0 : 1);                        // padding byte if name_len is even
    size_t sa_len = record_length - name_len - sizeof(struct iso_directory_record) - pad_len;
    if (record_length < sizeof(struct iso_directory_record))
    {
        fprintf(stderr, "check_rr: directory record length too small: %d\n", record_length);
        free(buf);
        return -EIO;
    }
    if (name_len != 1)
    {
        fprintf(stderr, "check_rr: file name length too big for . record: %d\n", name_len);
        free(buf);
        return -EIO;
    }
    /*if (sa_len < 0)
    {
        // probably something wrong with name_len
        fprintf(stderr, "check_rr: wrong name_len in directory entry: %d, record_length %d\n",
            name_len, record_length);
        free(buf);
        return -EIO;
    }*/

    if (sa_len >= 7)
    {
        struct rock_ridge *sue = (struct rock_ridge *) (((char *) record) +
            sizeof(struct iso_directory_record) +
            name_len + pad_len);

        int sue_sig = SIG(sue->signature[0], sue->signature[1]);
        int sue_len = sue->len;
        int sue_version = sue->version;

        if (sue_sig == SIG('S', 'P'))
        {
            if (sue_len != 7 || sue_version != 1 || sue->u.SP.magic[0] != 0xbe || sue->u.SP.magic[1] != 0xef)
            {
                // incorrect SP entry
                free(buf);
                return 0;
            }
            else
            {
                // got it!
                free(buf);
                return 1;
            }
        }
        else
        {
            // not SP record
            free(buf);
            return 0;
        }
    }
    else
    {
        // no space for SP record
        free(buf);
        return 0;
    }

    // should not happen
    free(buf);
    return 0;
}

int
CmgUtl::isofs_read_raw_block(
int block,
char *buf
)
{
    off_t off = block * _context.block_size + _context.block_offset + _context.file_offset;
    if (pthread_mutex_lock(&this->_fd_mutex))
    {
        int err = errno;
        perror("isofs_read_raw_block: can`l lock fd_mutex");
        return -err;
    }
    if (lseek(_context.fd, off, SEEK_SET) == -1)
    {
        perror("isofs_read_raw_block: can`t lseek()");
        pthread_mutex_unlock(&this->_fd_mutex);
        return -EIO;
    }
    size_t len = read(_context.fd, buf, _context.data_size);
    if (len != _context.data_size)
    {
        /*fprintf(stderr, "isofs_read_raw_block: can`t read full block, read only %d bytes from offset %d, %d required; errno %d, message %s\n",
            len, (int) off, _context.data_size, errno, strerror(errno));
        fprintf(stderr, "isofs_read_raw_block: huh? reading zeros beyond file end? someone want to save a penny?\n");*/
        memset(buf + len, 0, _context.data_size - len);
        // pthread_mutex_unlock(&this->_fd_mutex);
        // return -EIO;
    }
    pthread_mutex_unlock(&this->_fd_mutex);
    // printf("block %d, offset %d, read %d\n", block, (int) off, len);
    return len;
}

time_t
CmgUtl::isofs_date(
char *stamp,
int stamp_len
)
{
    struct tm tm;
    memset(& tm, 0, sizeof(struct tm));

    if (stamp_len == 7)                                               // ISO9660:9.1.5
    {
        tm.tm_year = stamp[0];
        tm.tm_mon = stamp[1] - 1;
        tm.tm_mday = stamp[2];
        tm.tm_hour = stamp[3];
        tm.tm_min = stamp[4];
        tm.tm_sec = stamp[5];
        tm.tm_isdst = -1;
        tm.tm_gmtoff = stamp[6] * 15 * 60;
    }                                                                 // ISO9660:8.4.26.1
    else if (stamp_len == 17)
    {
        fprintf(stderr, "isofs_date: 17 byte date isn`t supported for the moment, sorry\n");
        return 0;
    }
    else
    {
        fprintf(stderr, "isofs_date: unsupported date format, stamp_len %d\n", stamp_len);
        return 0;
    }

    if (tm.tm_gmtoff)
    {
        //         fprintf(stderr, "direntry2stat: non-zero timezone offset: %d\n", tm.tm_gmtoff);
    }
    time_t time = mktime(& tm);

    return time;
}

int
CmgUtl::isofs_direntry2stat(
struct stat *st,
isofs_inode *inode
)
{
    struct iso_directory_record *record = inode->record;

    // fill st_ino from block number where file start
    // since there is no files begin in the same block
    // st->st_ino = isonum_733(record->extent);
    // but some iso images save space by sharing content between several files
    // so it is better to save it unique
    st->st_ino = inode->st_ino;

    if (inode->ZF)                                                    // compressed file, report real (uncompressed) size
    {
        st->st_size = inode->real_size;
    }                                                                 // no zisofs compression
    else
    {
        st->st_size = isonum_733(record->size);
    }

    st->st_blocks = st->st_size / _context.data_size;                 // should not be to meaningful even for zisofs compression
    st->st_blksize = _context.data_size;
    st->st_nlink = 1;                                                 // always, even if rrip PX entry found

    if (inode->PX)                                                    // rrip PX entry found and in effect
    {
        st->st_mode = inode->st.st_mode;
        st->st_uid = inode->st.st_uid;
        st->st_gid = inode->st.st_gid;
    }
    else
    {
        /// TODO use hidden flag?
        if (ISO_FLAGS_DIR(record->flags))
        {
            st->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH |
                S_IXUSR | S_IXGRP | S_IXOTH;                          // dir, readabale and browsable by everyone
        }
        else
        {
            st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;      // regular, read by everyone
        }
    }

    if (inode->TF)                                                    // rrip TF entry found and in effect
    {
        st->st_atime = inode->st.st_atime;
        st->st_ctime = inode->st.st_ctime;
        st->st_mtime = inode->st.st_mtime;
    }
    else
    {
        if (!inode->ctime)
        {
            inode->ctime = this->isofs_date(record->date, 7);
        }

        st->st_atime = inode->ctime;
        st->st_ctime = inode->ctime;
        st->st_mtime = inode->ctime;
    }

    return 0;
}

char*
CmgUtl::isofs_fix_entry(
char *entry,
size_t len
)
{
    if (!_context.joliet_level)                                       // iso9660 names
    {
        char *sep2 = index(entry, ';');                               // find SEPARATOR2
        if (sep2)                                                     // remove remaining part
        {
            *sep2 = '\0';
        }
        char *sep1 = rindex(entry, '.');                              // find SEPARATOR1
        if (sep1 && sep1[1] == '\0')                                  // check if SEPARATOR1 is a last symbol in string
        {
            *sep1 = '\0';                                             // remove it
        }

        // this part is borrowed from linux kernel code
        // convert remaining ';' and '/' characters to dots
        // and lowercase characters in range A-Z
        char *p = entry;
        while (*p)
        {
            if (*p == ';' || *p == '/')
            {
                *p = '.';
            }
            else if (*p >= 'A' && *p <= 'Z')
            {
                *p |= 0x20;
            }

            p++;
        }

        return entry;
    }
    else
    {
        // initialize iconv descriptor
        iconv_t cd = iconv_open(this->_iocharset.c_str(), "UCS-2BE");
        if (cd < 0)
        {
            perror("iconv");
            return NULL;
        }

        char *inbuf = entry;
        size_t inbytesleft = len;

        char *outbuf = (char *) malloc(NAME_MAX);                     // this should be sufficient for our purposes
        if (!outbuf)
        {
            perror("Can`t malloc: ");
            return NULL;
        }
        char *outentry = outbuf;
        size_t outbytesleft = NAME_MAX;

        int rc = iconv(cd, & inbuf, & inbytesleft, & outbuf, & outbytesleft);
        size_t outlen = NAME_MAX - outbytesleft;
        outentry[outlen] = '\0';
        if (rc == -1)
        {
            // incorrect multibyte char or other iconv error -- return as much as possible anyway
            fprintf(stderr, "iconv on '%s': %s\n", outentry, strerror(errno));
            if (outlen == 0)
            {
                iconv_close(cd);
                free(entry);
                free(outentry);
                return NULL;
            }
            // try to recover
        }

        //         printf("outlen %d, outbytesleft %d, rc %d, outbuf %s\n", outlen, outbytesleft, rc, outentry);

        // borrowed from linux kernel isofs joliet code
        if (outlen > 2 && outentry[outlen - 2] == ';' && outentry[outlen - 1] == '1')
        {
            outentry[outlen - 2] = '\0';
        }
        if (outlen >= 2 && outentry[outlen - 1] == '.')
        {
            outentry[outlen - 1] = '\0';
        }

        free(entry);
        iconv_close(cd);

        return outentry;
    }
}

void
CmgUtl::isofs_free_inode(
isofs_inode *inode
)
{
    if (inode == NULL) return;
    if (inode->SL && inode->sl) free(inode->sl);
    if (inode->NM && inode->nm) free(inode->nm);
    if (inode->zf_blockptr) free(inode->zf_blockptr);
    if (inode->record) free(inode->record);
    free(inode);
}

int
CmgUtl::isofs_parse_zisofs_header(
isofs_inode *inode
)
{
    char *buf = (char *) malloc(_context.block_size);
    if (!buf)
    {
        perror("Can`t malloc: ");
        return -ENOMEM;
    }

    int block_num = isonum_733(inode->record->extent);
    int len = isofs_read_raw_block(block_num, buf);
    if (len < 0)
    {
        free(buf);
        return len;
    }
    if (len < (int)inode->zf_header_size)
    {
        fprintf(stderr, "isofs_parse_zisofs_header: too small block len %d\n", len);
        free(buf);
        return -EIO;
    }
    zf_file_header *zf_header = (zf_file_header *) buf;

    if (memcmp(zf_header->magic, zisofs_magic, sizeof(zisofs_magic)))
    {
        // invalid compressed file header
        free(buf);
        return 1;
    }

    size_t block_size = 1 << inode->zf_block_shift;

    inode->zf_nblocks = ((inode->real_size + inode->zf_header_size - 1) / block_size) + 1;

    size_t block_table_size = (inode->zf_nblocks + 1) * 4;
    if (!inode->zf_blockptr)
    {
        inode->zf_blockptr = (int *) malloc(block_table_size);
        if (!inode->zf_blockptr)
        {
            perror("Can`t malloc: ");
            return -ENOMEM;
        }
    }

    // copy offset table into memory buffer, maintaining iso9660 block boundaries

    int block_table_left = block_table_size;
    int block_table_total = 0;
    int block_table_shift = inode->zf_header_size;

    while (block_table_left)
    {
        size_t block_table_chunk =
            (block_table_left < (int)_context.data_size - block_table_shift ?
            block_table_left : _context.data_size - block_table_shift);

        /*        printf("isofs_parse_sa: block_table_size: %d, block_table_left: %d, block_table_total %d, block_table_shift %d, block_table_chunk %d\n",
                                    block_table_size, block_table_left, block_table_total, block_table_shift, block_table_chunk);*/

        memcpy(((char *) inode->zf_blockptr) + block_table_total, buf + block_table_shift, block_table_chunk);

        block_table_left -= block_table_chunk;
        block_table_total += block_table_chunk;
        block_table_shift = 0;

        // read next block
        block_num += 1;
        len = isofs_read_raw_block(block_num, buf);
        if (len < 0)
        {
            free(buf);
            return len;
        }

        /*        printf("isofs_parse_sa: block_num: %d, len: %d\n",
                                    block_num, len);*/
    }

    /*    printf("isofs_parse_zisofs_header: real size %d, header size %d, nblocks %d, block size %d\n",
                    inode->real_size, inode->zf_header_size,
                    inode->zf_nblocks, block_size);
                int i;
                for (i = 0; i <= inode->zf_nblocks; i++) {
                    printf("zf block table entry %d have value %d\n", i, inode->zf_blockptr[i]);
                }*/

    // all information for compressed file we have already in ZF entry
    // so just signal what this is really compressed file
    free(buf);
    return 0;
}

int
CmgUtl::isofs_parse_sa(
isofs_inode *inode,
char *sa,
size_t sa_len
)
{
    int cont_block = 0;
    int cont_offset = 0;
    int cont_size = 0;

    int remaining = sa_len;
    while (remaining > 4)                                             // susp 4.
    {
        //         printf("parse_sa: sa offset %d, remaining %d\n", sa_len - remaining, remaining);
        struct rock_ridge *sue = (struct rock_ridge *) (sa + sa_len - remaining);
        int sue_sig = SIG(sue->signature[0], sue->signature[1]);
        int sue_len = sue->len;
        int sue_version = sue->version;
        /*        printf("parse_sa: signature %c%c, sue_len %d, sue_version %d\n",
                                    sue->signature[0], sue->signature[1], sue_len, sue_version);*/

        int known_sue = 1;

        switch (sue_sig)
        {
            case SIG('S', 'P'):                                       // susp 5.3
                if (sue_len != 7 || sue_version != 1 || sue->u.SP.magic[0] != 0xbe || sue->u.SP.magic[1] != 0xef)
                {
                    // incorrect SP entry
                    fprintf(stderr,
                        "parse_sa: incorrect SP entry: sue_len %d, sue_version %d, magic %c%c\n",
                        sue_len, sue_version, sue->u.SP.magic[0], sue->u.SP.magic[1]);
                    _context.susp = 0;
                    return 0;
                }
                else
                {
                    _context.susp = 1;
                    _context.susp_skip = sue->u.SP.skip;
                }
                //                 printf("parse_sa: SP entry, skip %d\n", sue->u.SP.skip);
                break;
            case SIG('C', 'E'):                                       // susp 5.1
                if (sue_len != 28 || sue_version != 1)
                {
                    // incorrect entry, skip
                    fprintf(stderr,
                        "parse_sa: incorrect CE entry: sue_len %d, sue_version %d\n",
                        sue_len, sue_version);
                }                                                     // CE entry already found
                else if (cont_block != 0)
                {
                    fprintf(stderr,
                        "parse_sa: duplicate CE entry, skipping, sue_len %d, sue_version %d\n",
                        sue_len, sue_version);
                }
                else
                {
                    cont_block = isonum_733(sue->u.CE.extent);
                    cont_offset = isonum_733(sue->u.CE.offset);
                    cont_size = isonum_733(sue->u.CE.size);

                    if (cont_block < 16)
                    {
                        // continuation area can`t be there
                        fprintf(stderr,
                            "parse_sa: wrong continuation area extent: %d, cont_offset %d, cont_size %d\n",
                            cont_block, cont_offset, cont_size);
                        cont_block = 0;                               // do not process it
                    }
                    else if (cont_offset + cont_size > (int)_context.data_size)
                    {
                        // something wrong with continuation area offset and/or size
                        fprintf(stderr,
                            "parse_sa: wrong continuation area: extent %d, cont_offset %d, cont_size %d\n",
                            cont_block, cont_offset, cont_size);
                        cont_block = 0;                               // do not process it
                    }
                    else
                    {
                        /*                        printf("parse_sa: CE entry, extent %d, offset %d, size %d\n",
                                                                                                    cont_block, cont_offset, cont_size);*/
                    }
                }
                break;
            case SIG('E', 'R'):                                       // susp 5.5
                if (sue_version != 1)
                {
                    // incorrect entry, skip
                    fprintf(stderr,
                        "parse_sa: incorrect ER entry: sue_len %d, sue_version %d\n",
                        sue_len, sue_version);
                }
                else
                {
                    int len_id = sue->u.ER.len_id;
                    int len_des = sue->u.ER.len_des;
                    int len_src = sue->u.ER.len_src;
                    int ext_ver = sue->u.ER.ext_ver;
                    if (len_id + len_des + len_src + 8 > sue_len)
                    {
                        fprintf(stderr,
                            "parse_sa: incorrect ER entry: sue_len %d, sue_version %d, len_id %d, len_des %d, len_src %d, ext_ver %d\n",
                            sue_len, sue_version, len_id, len_des, len_src, ext_ver);
                    }
                    else
                    {
                        char id[256];
                        char des[256];
                        char src[256];

                        strncpy(id, sue->u.ER.data, len_id);
                        id[len_id] = '\0';
                        strncpy(des, sue->u.ER.data + len_id, len_des);
                        des[len_des] = '\0';
                        strncpy(src, sue->u.ER.data + len_id + len_des, len_src);
                        src[len_src] = '\0';

                        /*                        printf("parse_sa: ER entry:\n\t id: %s\n\tdes: %s\n\tsrc: %s\n\tver: %d\n",
                                                                                                    id, des, src, ext_ver);*/
                    }
                }
                break;
            case SIG('R', 'R'):
                {
                // unused
                isonum_711((unsigned char *) sue->u.RR.flags);
                /*                    printf("parse_sa: RR entry, sue_version %d, sue_len %d, flags %d\n",
                                                                        sue_version, sue_len, rr_flags);*/
                }
                break;
            case SIG('P', 'X'):                                       // rrip 4.1.1
                // according to rrip 4.1.1, length of PX record should be exactly 44
                // but linux kernel and mkisofs seems to be happy with length 36,
                // where 'serial number' are not presented
                // (or i`m looking at outdated draft.. :-)
                if ((sue_len != 44 && sue_len != 36) || sue_version != 1)
                {
                    // incorrect entry, skip
                    fprintf(stderr,
                        "parse_sa: incorrect PX entry: sue_len %d, sue_version %d\n",
                        sue_len, sue_version);
                }
                else
                {
                    mode_t mode = isonum_733(sue->u.PX.mode);
                    nlink_t nlink = isonum_733(sue->u.PX.n_links);
                    uid_t uid = isonum_733(sue->u.PX.uid);
                    gid_t gid = isonum_733(sue->u.PX.gid);
                    /*                    printf("parse_sa: PX entry, sue_version %d, sue_len %d, mode %d, nlinks %d, uid %d, gid %d\n",
                                                                                    sue_version, sue_len, mode, nlink, uid, gid);*/
                    inode->st.st_mode = mode;
                    inode->st.st_nlink = nlink;
                    inode->st.st_uid = uid;
                    inode->st.st_gid = gid;
                    inode->PX = 1;
                    /// TODO check if entry length == 44 and set st_ino field from 'file serial number'?
                }
                break;
            case SIG('S', 'L'):                                       // rrip 4.1.3
                if (sue_version != 1)
                {
                    // incorrect entry, skip
                    fprintf(stderr,
                        "parse_sa: incorrect SL entry: sue_len %d, sue_version %d\n",
                        sue_len, sue_version);
                }
                else if (inode->SL)
                {
                    fprintf(stderr,
                        "parse_sa: SL entry already in effect, sue_len %d, sue_version %d\n",
                        sue_len, sue_version);
                }
                else
                {
                    int sl_flags = sue->u.SL.flags;
                    int max_components = (sue_len - 5) / sizeof(struct SL_component);

                    if (max_components < 1)
                    {
                        fprintf(stderr,
                            "parse_sa: SL entry found, but no components: sue_len %d, sue_version %d\n",
                            sue_len, sue_version);
                    }
                    else
                    {
                        int c;
                        int c_offset = 0;
                        int c_errors = 0;
                        for (c = 0; c < max_components; c++)
                        {
                            struct SL_component *comp =
                                (struct SL_component *)
                                (((char *) & sue->u.SL.link) + c_offset);

                            int sl_c_flags = comp->flags;
                            int sl_c_len = comp->len;

                            if (c_offset + 5 >= sue_len)
                            {
                                // according to rrip, we must stop if CONTINUE flag isn`t set
                                // according to linux kernel isofs code and images produced witj mkisofs
                                // we need to continue while there is space in SL entry for next component
                                break;

                                // strict rrip code:
                                // fprintf(stderr,
                                //     "parse_sa: invalid SL component: sue_len %d, sue_version %d, sl_flags %d, sl_c_flags %d, sl_c_len %d\n",
                                //     sue_len, sue_version, sl_flags, sl_c_flags, sl_c_len);
                                // c_errors++;
                                // break;

                                /// TODO find _working_ rrip specification
                            }

                            int c_len = 0;
                            char c_separator = 0;
                            if (!inode->sl_len)                       // nothing found previoustly
                            {
                            }                                         // previous SL component was ROOT
                            else if (inode->sl_len == 1 && inode->sl[0] == '/')
                            {
                                // no need for separator after ROOT component
                            }
                            else
                            {
                                c_len += 1;                           // place for '/' separator
                                c_separator = '/';
                            }
                            if (sl_c_flags & (1 << 1))                // CURRENT component
                            {
                                c_len += 1;                           // place for '.' character
                            }                                         // PARENT component
                            else if (sl_c_flags & (1 << 2))
                            {
                                c_len += 2;                           // place for ".." characters
                            }
                            else
                            {
                                c_len += sl_c_len;
                            }

                            if (c_len + inode->sl_len + 1 > PATH_MAX)
                            {
                                fprintf(stderr,
                                    "parse_sa: too long symlink found: sue_len %d, sue_version %d, sl_flags %d, sl_c_flags %d, sl_c_len %d, c_len %d\n",
                                    sue_len, sue_version, sl_flags, sl_c_flags, sl_c_len, c_len);
                                c_errors++;
                                break;
                            }

                            if (!inode->sl)
                            {
                                inode->sl = (char *) malloc(PATH_MAX);
                                if (!inode->sl)
                                {
                                    perror("Can`t malloc: ");
                                    return -ENOMEM;
                                }
                            }

                            if (c_separator)
                            {
                                inode->sl[inode->sl_len] = c_separator;
                                inode->sl_len += 1;
                            }

                            if (sl_c_flags & (1 << 1))                // CURRENT component
                            {
                                inode->sl[inode->sl_len] = '.';
                                inode->sl_len += 1;
                                inode->sl[inode->sl_len] = '\0';
                                /*                                printf("parse_sa: SL CURRENT component, sl_c_flags %d, sl_c_len %d, sl_len %d, sl %s\n",
                                                                                                                                    sl_c_flags, sl_c_len, inode->sl_len, inode->sl);*/
                            }                                         // PARENT component
                            else if (sl_c_flags & (1 << 2))
                            {
                                inode->sl[inode->sl_len] = '.';
                                inode->sl_len += 1;
                                inode->sl[inode->sl_len] = '.';
                                inode->sl_len += 1;
                                inode->sl[inode->sl_len] = '\0';
                                /*                                printf("parse_sa: SL PARENT component, sl_c_flags %d, sl_c_len %d, sl_len %d, sl %s\n",
                                                                                                                                    sl_c_len, inode->sl_len, inode->sl);*/
                            }                                         // ROOT component (?! does not documented at all)
                            else if (sl_c_flags & (1 << 3))
                            {
                                inode->sl[inode->sl_len] = '/';
                                inode->sl_len += 1;
                                inode->sl[inode->sl_len] = '\0';
                                /*                                printf("parse_sa: SL ROOT component, sl_c_flags %d, sl_c_len %d, sl_len %d, sl %s\n",
                                                                                                                                    sl_c_len, inode->sl_len, inode->sl);*/
                            }
                            else
                            {
                                strncpy(inode->sl + inode->sl_len, comp->text, sl_c_len);
                                inode->sl_len += sl_c_len;
                                inode->sl[inode->sl_len] = '\0';
                                /*                                printf("parse_sa: SL component, sl_c_flags %d, sl_c_len %d, sl_len %d, sl %s\n",
                                                                                                                                    sl_c_flags, sl_c_len, inode->sl_len, inode->sl);*/
                            }

                            // according to rrip, we must stop if CONTINUE flag isn`t set
                            // according to linux kernel isofs code and images produced witj mkisofs
                            // we need to continue while there is space in SL entry for next component
                            c_offset += (2 + sl_c_len);

                            // strict rrip code:
                            // if (sl_c_flags & 1) { // CONTINUE
                            //     c_offset += 2 + sl_c_len;
                            // } else {
                            //     printf("parse_sa: SL final component, sl_c_len %d, sl_c_flags %d, sl_len %d, sl %s\n",
                            //         sl_c_len, sl_c_flags, inode->sl_len, inode->sl);
                            //     break;
                            // }
                        }

                        if (c_errors)
                        {
                            //                             printf("parse_sa: errors found while processing SL components, cleaning\n");
                            if (inode->sl)
                            {
                                free(inode->sl);
                            }
                            inode->sl_len = 0;
                            inode->SL = 0;
                        }
                        else if (!(sl_flags & 1) && inode->sl)
                        {
                            /*                            printf("parse_sa: SL entry (final), sue_len %d, sue_version %d, sl_len %d, sl %s\n",
                                                                                                                    sue_len, sue_version, inode->sl_len, inode->sl);*/
                            inode->SL = 1;
                        }
                        else if (!(sl_flags & 1) && !inode->sl)
                        {
                            fprintf(stderr, "parse_sa: final SL entry found, but no SL components, cleaning\n");
                            if (inode->sl)
                            {
                                free(inode->sl);
                            }
                            inode->sl_len = 0;
                            inode->SL = 0;
                        }
                        else if (inode->sl)
                        {
                            /*                            printf("parse_sa: SL entry, sue_len %d, sue_version %d, sl_len %d, sl %s\n",
                                                                                                                    sue_len, sue_version, inode->sl_len, inode->sl);*/
                        }
                        else
                        {
                            fprintf(stderr, "parse_sa: empty SL entry?\n");
                        }
                    }
                }
                break;
            case SIG('N', 'M'):                                       // rrip 4.1.4
                if (sue_version != 1)
                {
                    // incorrect entry, skip
                    fprintf(stderr,
                        "parse_sa: incorrect NM entry: sue_len %d, sue_version %d\n",
                        sue_len, sue_version);
                }
                else
                {
                    int nm_flags = sue->u.NM.flags;
                    if (nm_flags & (1 << 1))                          // CURRENT bit
                    {
                        /*                        printf("parse_sa: NM CURRENT entry, sue_version %d, sue_len %d, flags %d\n",
                                                                                                    sue_version, sue_len, nm_flags);*/
                    }                                                 // PARENT bit
                    else if (nm_flags & (1 << 2))
                    {
                        /*                        printf("parse_sa: NM PARENT entry, sue_version %d, sue_len %d, flags %d\n",
                                                                                                    sue_version, sue_len, nm_flags);*/
                    }
                    else
                    {
                        if (sue_len - 5 + inode->nm_len > NAME_MAX - 1)
                        {
                            fprintf(stderr,
                                "parse_sa: too long NM entry: %d\n",
                                sue_len - 5 + inode->nm_len);
                        }
                        else if (inode->NM)
                        {
                            fprintf(stderr,
                                "parse_sa: NM entry already in effect, sue_len %d, sue_version %d\n",
                                sue_len, sue_version);
                        }
                        else
                        {
                            if (!inode->nm)
                            {
                                inode->nm = (char *) malloc(NAME_MAX);
                                if (!inode->nm)
                                {
                                    perror("Can`t malloc: ");
                                    return -ENOMEM;
                                }
                            }

                            strncpy(inode->nm + inode->nm_len, sue->u.NM.name, sue_len - 5);
                            inode->nm_len += sue_len - 5;
                            inode->nm[inode->nm_len] = '\0';

                            if (!nm_flags & 1)                        // CONTINUE bit
                            {
                                inode->NM = 1;
                                /*                                printf("parse_sa: NM entry (final), flags %d, len %d, name %s\n",
                                                                                                                                    nm_flags, sue_len - 5, inode->nm);*/
                            }
                            else
                            {
                                /*                                printf("parse_sa: NM entry (part), flags %d, len %d, name %s\n",
                                                                                                                                    nm_flags, sue_len - 5, inode->nm);*/
                            }
                        }
                    }
                }
                break;
            case SIG('C', 'L'):                                       // rrip 4.1.5.1
                if (sue_version != 1 || sue_len != 12)
                {
                    // incorrect entry, skip
                    fprintf(stderr,
                        "parse_sa: incorrect CL entry: sue_len %d, sue_version %d\n",
                        sue_len, sue_version);
                }
                else
                {
                    int cl_block = isonum_733(sue->u.CL.location);
                    /*                    printf("parse_sa: CL entry, block %d\n",
                                                                                                cl_block);*/
                    inode->CL = 1;
                    inode->cl_block = cl_block;

                    // read block pointed by CL record and process first directory entry
                    char *buf = (char *) malloc(_context.data_size);
                    if (!buf)
                    {
                        perror("Can`t malloc: ");
                        return -ENOMEM;
                    }

                    int rc = isofs_read_raw_block(inode->cl_block, buf);
                    if (rc < 0)
                    {
                        free(buf);
                        return rc;
                    }

                    struct iso_directory_record *record = (struct iso_directory_record *) buf;
                    size_t record_length = isonum_711((unsigned char *) record->length);
                    size_t name_len = isonum_711(record->name_len);
                    size_t pad_len = ((name_len & 1) ? 0 : 1);        // padding byte if name_len is even
                    size_t sa_len = record_length - name_len - sizeof(struct iso_directory_record) - pad_len;
                    /*            printf("boff %d, record length %d, name_len %d, pad_len %d, sa_len %d\n",
                                                                                    (int) boff, record_length, name_len, pad_len, sa_len);*/
                    if (record_length < sizeof(struct iso_directory_record))
                    {
                        fprintf(stderr, "parse_sa: CL entry: directory record length too small: %d\n", record_length);
                        free(buf);
                        return -EIO;
                    }
                    if (name_len != 1)
                    {
                        fprintf(stderr, "parse_sa: file name length too big for . record: %d\n", name_len);
                        free(buf);
                        return -EIO;
                    }
                    /*if (sa_len < 0)
                    {
                        // probably something wrong with name_len
                        fprintf(stderr, "parse_sa: CL record: wrong name_len in directory entry: %d, record_length %d\n",
                            name_len, record_length);
                        free(buf);
                        return -EIO;
                    }*/

                    // ignoring anything from original record
                    struct iso_directory_record *cl_record =
                        (struct iso_directory_record *) malloc(sizeof(struct iso_directory_record));
                    if (!cl_record)
                    {
                        perror("Can`t malloc: ");
                        return -ENOMEM;
                    }
                    memcpy(cl_record, record, sizeof(struct iso_directory_record));

                    // drop existing record
                    if (inode->record)
                    {
                        free(inode->record);
                    }

                    // replace record with new one
                    inode->record = cl_record;

                    // parse sa entries from relocated directory . record
                    rc = this->isofs_parse_sa(inode,
                        ((char *) record) +
                        sizeof(struct iso_directory_record) +
                        name_len + pad_len + _context.susp_skip,
                        sa_len);
                    if (rc)
                    {
                        free(buf);
                        return rc;
                    }

                    free(buf);
                }
                break;
            case SIG('P', 'L'):                                       // rrip 4.1.5.2
                if (sue_version != 1 || sue_len != 12)
                {
                    // incorrect entry, skip
                    fprintf(stderr,
                        "parse_sa: incorrect PL entry: sue_len %d, sue_version %d\n",
                        sue_len, sue_version);
                }
                else
                {
                    int pl_block = isonum_733(sue->u.PL.location);
                    /*                    printf("parse_sa: PL entry, block %d\n",
                                                                                                pl_block);*/
                    // probably we don`t need process PL record with FUSE
                    inode->PL = 1;
                    inode->pl_block = pl_block;
                }
                break;
            case SIG('R', 'E'):                                       // rrip 4.1.5.3
                if (sue_version != 1 || sue_len != 4)
                {
                    // incorrect entry, skip
                    fprintf(stderr,
                        "parse_sa: incorrect RE entry: sue_len %d, sue_version %d\n",
                        sue_len, sue_version);
                }
                else
                {
                    //                     printf("parse_sa: RE entry\n");
                    inode->RE = 1;
                }
                break;
            case SIG('T', 'F'):                                       // rrip 4.1.6
                if (sue_version != 1)
                {
                    // incorrect entry, skip
                    fprintf(stderr,
                        "parse_sa: incorrect TF entry: sue_len %d, sue_version %d\n",
                        sue_len, sue_version);
                }
                else
                {
                    int tf_flags = sue->u.TF.flags;
                    int stamp_length;
                    if (tf_flags & TF_LONG_FORM)
                    {
                        // iso9660:8.4.26.1 format
                        stamp_length = 17;
                    }
                    else
                    {
                        // iso9660:9.1.5 format
                        stamp_length = 7;
                    }

                    time_t ctime = 0;
                    time_t mtime = 0;
                    time_t atime = 0;

                    int stamp_no = 0;
                    // ctime can be stored as CREATION time
                    if (tf_flags & TF_CREATE)
                    {
                        ctime = this->isofs_date(((char *) sue) + 5 + stamp_length * stamp_no, stamp_length);
                        stamp_no++;
                    }
                    if (tf_flags & TF_MODIFY)
                    {
                        mtime = this->isofs_date(((char *) sue) + 5 + stamp_length * stamp_no, stamp_length);
                        stamp_no++;
                    }
                    if (tf_flags & TF_ACCESS)
                    {
                        atime = this->isofs_date(((char *) sue) + 5 + stamp_length * stamp_no, stamp_length);
                        stamp_no++;
                    }
                    // ctime should be stored in ATTRIBUTES stamp according to rrip 4.1.6
                    if (tf_flags & TF_ATTRIBUTES)
                    {
                        ctime = this->isofs_date(((char *) sue) + 5 + stamp_length * stamp_no, stamp_length);
                        stamp_no++;
                    }
                    // other fields have no meaning for us

                    /*                    printf("parse_sa: TF entry, sue_version %d, sue_len %d, ctime %d, mtime %d, atime %d\n",
                                                                                    sue_version, sue_len, ctime, mtime, atime);*/
                    inode->st.st_ctime = ctime;
                    inode->st.st_mtime = mtime;
                    inode->st.st_atime = atime;
                    inode->TF = 1;
                }
                break;
            case SIG('S', 'F'):                                       // rrip 4.1.7
                if (sue_version != 1 || sue_len != 21)
                {
                    // incorrect entry, skip
                    fprintf(stderr,
                        "parse_sa: incorrect SF entry: sue_len %d, sue_version %d\n",
                        sue_len, sue_version);
                }
                else
                {
                    /// TODO does anyone support SF entries? linux isofs code does not..
                    fprintf(stderr,
                        "parse_sa: SF entries (sparse files) are unsupported in this version, sorry..\n");
                }
                break;
            case SIG('Z', 'F'):                                       // non-standard linux extension (zisofs)
                if (sue_version != 1 || sue_len != 16)
                {
                    // incorrect entry, skip
                    fprintf(stderr,
                        "parse_sa: incorrect ZF entry: sue_len %d, sue_version %d\n",
                        sue_len, sue_version);
                }
                else if (SIG(sue->u.ZF.algorithm[0], sue->u.ZF.algorithm[1]) == SIG('p', 'z'))
                {
                    inode->zf_algorithm[0] = sue->u.ZF.algorithm[0];
                    inode->zf_algorithm[1] = sue->u.ZF.algorithm[1];
                    inode->zf_header_size = ((unsigned char) sue->u.ZF.parms[0]) << 2;
                    inode->zf_block_shift = (unsigned char) sue->u.ZF.parms[1];
                    inode->zf_size = (inode->st.st_size ? inode->st.st_size : isonum_733(inode->record->size));
                    inode->real_size = isonum_733(sue->u.ZF.real_size);
                    // check if file is really compressed, ignore ZF entry otherwise
                    int rc = this->isofs_parse_zisofs_header(inode);
                    if (rc == 0)
                    {
                        //                         printf("parse_sa: ZF entry found, algorithm %02x%02x, header size %d, block shift %d, compressed size %d, real size %d\n",
                        //                             inode->zf_algorithm[0], inode->zf_algorithm[1],
                        //                             inode->zf_header_size, inode->zf_block_shift,
                        //                             inode->zf_size, inode->real_size);
                        inode->ZF = 1;
                    }
                    else if (rc > 0)
                    {
                        fprintf(stderr, "parse_sa: ZF entry found, but file is not really compressed\n");
                    }                                                 // some error occur
                    else
                    {
                        return rc;
                    }
                }
                else
                {
                    fprintf(stderr,
                        "parse_sa: unknown ZF compression algorithm %c%c, sorry..\n",
                        sue->u.ZF.algorithm[0], sue->u.ZF.algorithm[1]);
                }
                break;
            default:
                known_sue = 0;
                fprintf(stderr, "parse_sa: unknown entry '%c%c', sue_sig %d, sue_version %d, sue_len %d\n",
                    sue->signature[0], sue->signature[1],
                    sue_sig, sue_version, sue_len);
                break;
        }

        if (sue_len >= 4 && known_sue)
        {
            remaining -= sue_len;
        }
        else if (known_sue)
        {
            fprintf(stderr, "parse_sa: inappropriate susp entry length: %d, signature %c%c\n",
                sue_len, sue->signature[0], sue->signature[1]);
            return -EIO;
        }                                                             // probably there are no more susp entries
        else
        {
            return 0;
        }
    }

    // process continuation area if found
    if (cont_block)
    {
        char *buf = (char *) malloc(_context.data_size);
        if (!buf)
        {
            perror("Can`t malloc: ");
            return -ENOMEM;
        }
        int rc = isofs_read_raw_block(cont_block, buf);
        if (rc < 0)
        {
            free(buf);
            return rc;
        }
        /*        printf("parse_sa: deep into CE, extent %d, offset %d, size %d\n",
                                    cont_block, cont_offset, cont_size);*/
        rc = this->isofs_parse_sa(inode, buf + cont_offset, cont_size);
        if (rc)
        {
            free(buf);
            return rc;
        }

        free(buf);
    }

    return 0;
}

int
CmgUtl::isofs_real_read_zf(
isofs_inode *inode,
char *out_buf,
size_t size,
off_t offset
)
{
    int zf_block_size = 1 << inode->zf_block_shift;
    int zf_start = offset / zf_block_size;
    int zf_end = (offset + size) / zf_block_size;
    // Protection against cornercase when read request is exactly equal to the uncompressed file size.
    // PATCH #1 -- Ryan Thomas 2007-06-13
    if ((offset+size ) % zf_block_size==0) zf_end--;
    int shift = offset % zf_block_size;

    //     printf("zf_start %d, zf_end %d, size %d, offset %d, shift %d\n",
    //         zf_start, zf_end, size, (int) offset, shift);

    // protection against some ununderstandable errors
    if (zf_start >= inode->zf_nblocks) zf_start = inode->zf_nblocks-1;
    if (zf_end >= inode->zf_nblocks) zf_end = inode->zf_nblocks-1;
    if (zf_end < 0 || zf_start < 0) return 0;

    unsigned char *cbuf = (unsigned char *) malloc(zf_block_size * 2);
    if (!cbuf)
    {
        perror("Can`t malloc: ");
        return -ENOMEM;
    }
    unsigned char *ubuf = (unsigned char *) malloc(zf_block_size);
    if (!ubuf)
    {
        perror("Can`t malloc: ");
        return -ENOMEM;
    }

    size_t total_size = 0;
    size_t size_left = size;

    int base_offset = isonum_733(inode->record->extent) * _context.data_size;

    int i;
    for (i = zf_start; i <= zf_end; i++)
    {
        int block_offset = isonum_731((char *) (& inode->zf_blockptr[i]));
        int block_end = isonum_731((char *) (& inode->zf_blockptr[i + 1]));
        int block_size = block_end - block_offset;

        if (block_size == 0)                                          // sort of sparce file block
        {
            size_t payload_size = (zf_block_size - shift < (int)size_left?(int)(zf_block_size-shift):(int)size_left);
            memset(out_buf + total_size, 0, payload_size);

            total_size += payload_size;
            size_left -= payload_size;
        }
        else if (block_size > zf_block_size * 2)
        {
            fprintf(stderr, "isofs_real_read_zf: compressed block size bigger than uncompressed block size * 2, something is wrong there.. block size %d, uncompressed block size %d\n",
                block_size, zf_block_size);
            free(cbuf);
            free(ubuf);
            return -EIO;
        }
        else
        {
            // we do not use isofs_read_raw_block() there because it is simpler to access
            // compressed blocks directly

            int image_off = base_offset + block_offset;

            if (pthread_mutex_lock(&this->_fd_mutex))
            {
                int err = errno;
                perror("isofs_real_read_zf: can`l lock fd_mutex");
                free(cbuf);
                free(ubuf);
                return -err;
            }
            if (lseek(_context.fd, image_off, SEEK_SET) == -1)
            {
                perror("isofs_real_read_zf: can`t lseek()");
                pthread_mutex_unlock(&this->_fd_mutex);
                free(cbuf);
                free(ubuf);
                return -EIO;
            }
            size_t len = read(_context.fd, cbuf, block_size);
            if ((int)len != block_size)
            {
                fprintf(stderr, "isofs_real_read_zf: can`t read full block, errno %d, message %s\n",
                    errno, strerror(errno));
                pthread_mutex_unlock(&this->_fd_mutex);
                free(cbuf);
                free(ubuf);
                return -EIO;
            }
            pthread_mutex_unlock(&this->_fd_mutex);

            // compressed block is read from disk, now uncompress() it

            uLongf usize = zf_block_size;
            int rc = uncompress(ubuf, &usize, cbuf, block_size);
            if (rc != Z_OK)
            {
                free(cbuf);
                free(ubuf);
                fprintf(stderr, "isofs_real_read_zf: uncompress() error %i: %s\n", rc, strerror(rc));
                return -rc;
            }

            // ubuf contain uncompressed data, usize contain uncompressed block size

            size_t payload_size = (usize - shift < size_left ? usize - shift : size_left);
            memcpy(out_buf + total_size, ubuf + shift, payload_size);

            total_size += payload_size;
            size_left -= payload_size;
        }

        // shift is only meaningful for first block
        shift = 0;
    }

    free(cbuf);
    free(ubuf);

    //     printf("total size %d\n", total_size);

    return total_size;
}

char*
CmgUtl::dstr(
char* str,
const char* src,
int len
)
{
    int i;
    strncpy(str, src, len);
    str[len] = '\0';
    for (i=len-1; i>=0; --i)
    {
        if (str[i] == '\0' || str[i] == ' ' || str[i] == '\t' || str[i] == '\r' || str[i] == '\n') str[i] = '\0';
        else return str;
    }
    return str;
}

